# TODO:
# - some Null fields are not configured correctly.

import os
import re
import datetime
import logging

from django.db import models
from django.conf import settings

from browsertests.specs.models import Section

log = logging.getLogger(__name__)

# XXX move elsewhere?
class Status(models.Model):
    BUSY = -1
    generation = models.IntegerField(null=True, blank=True)

    class Meta:
        verbose_name_plural = 'statuses'

class Flag(models.Model):
    name = models.CharField(max_length=90, primary_key=True)

    def __unicode__(self):
        return self.name

class Tag(models.Model):
    name = models.CharField(max_length=90, primary_key=True)

    def __unicode__(self):
        return self.name

class Test(models.Model):
    TYPE_CHOICES = (
        ("mochitest", "MochiTest"),
        ("layouttest", "LayoutTest"),
        ("reftest", "RefTest"),
    )

    id = models.CharField(max_length=200, primary_key=True)
    full_id = models.CharField(max_length=255, blank=True)
    sections = models.ManyToManyField(Section, blank=True)
    tags = models.ManyToManyField(Tag, blank=True)
    flags = models.ManyToManyField(Flag, blank=True)
    type = models.CharField(max_length=60, choices=TYPE_CHOICES)
    # XXX use that?
    #modif_date = models.DateTimeField(auto_now_add=True)
    modif_date = models.DateTimeField(default=datetime.datetime.now)
    version = models.IntegerField(default=0)
    upstream_version = models.CharField(max_length=255, blank=True)
    enabled = models.BooleanField(default=True)
    deleted = models.BooleanField(default=False)
    url = models.CharField(max_length=255, blank=True)
    file = models.CharField(max_length=255, blank=True)
    hash = models.CharField(max_length=96, blank=True)

    # mochitests
    pass_count = models.IntegerField(default=0)
    todo_count = models.IntegerField(default=0)

    # reftests
    equal = models.BooleanField(default=True)
    expected = models.IntegerField(null=True, blank=True)
    failure_type = models.CharField(max_length=150, blank=True)
    url2 = models.CharField(max_length=255, blank=True)
    file2 = models.CharField(max_length=255, blank=True)

    class Meta:
        ordering = ['id']

    # A note about the m2m attributes and methods here.  m2m stands for Many to
    # Many.  During test import, temporary tests need to be created and then
    # compared against the test already stored in the database in order to check
    # if the existing tests need to be updated or not.
    # Every orm object with the same identifier will share their many to many
    # attributes.  It means that if you create a temporary orm object with the
    # same identifier as an object already saved in the database, modifying its
    # m2m attributes also modifies the object stored in the database.
    #
    # To get around this issue, the m2m fields are temporarily stored on the orm
    # object during import and when comparing expected and actual objects in the
    # unit test.  This is achieved by calling the load_m2m() to load the m2m
    # attributes on the object.  They can then be modified using the attribute
    # name underscore prefixed (_tags for instance).  When the object is saved,
    # these temporary m2m values are saved back to "real" m2m attributes (and
    # saved in the db).
    M2M_TEMP_ATTRS = (("flags", Flag), ("tags", Tag), ("sections", Section))

    _m2m_obj_cache = {}

    def __init__(self, *args, **kwargs):
        super(Test, self).__init__(*args, **kwargs)
        for attr, model in self.M2M_TEMP_ATTRS:
            setattr(self, "_" + attr, set())

    def __unicode__(self):
        return (self.full_id if self.full_id else self.id)

    def load_m2m(self):
        for attr, model in self.M2M_TEMP_ATTRS:
            setattr(self, "_" + attr, set((v[0] for v in getattr(self, attr).values_list())))

    def _get_obj(self, model, pk):
        key = (model, pk)
        if key in self._m2m_obj_cache:
            return self._m2m_obj_cache[key]
        self._m2m_obj_cache[key] = model.objects.get_or_create(pk=pk)[0]
        return self._m2m_obj_cache[key]

    def save_m2m_attr(self, attr, model):
        pks = getattr(self, "_" + attr)
        objs = [self._get_obj(model, pk) for pk in pks]
        setattr(self, attr, objs)

    def save(self, force_insert=False, force_update=False):
        for attr, model in self.M2M_TEMP_ATTRS:
            self.save_m2m_attr(attr, model)
        super(Test, self).save(force_insert, force_update)

    @models.permalink
    def get_absolute_url(self):
        return ('test-detail', [str(self.id)])

    def is_equal(self, other):
        if not isinstance(other, self.__class__):
            return False
        IGNORED_KEYS = ("modif_date", "version", "_sections", "enabled")

        if self.id != other.id:
            log.debug("_tests_equal: id differ")
            return False
        for key in self.__dict__.iterkeys():
            if key in IGNORED_KEYS:
                continue
            if getattr(self, key) != getattr(other, key):
                log.debug("_tests_equal (%s): differ by key %s", self.id, key)
                return False
        return True

    def _get_content(self, f):
        path = os.path.join(settings.TESTS_PATH , f)
        if not os.path.splitext(path)[1] in (".html", ".svg", ".xhtml", ".xul",
                                             ".txt", ".xml"):
            return "BINARY CONTENT"

        # XXX what to do with non ascii?
        #table  = "".join(map(chr, range(128))) + "?" * 128
        try:
            content = open(path).read()
        except IOError, e:
            content = "ERROR %s" % e
        return content

    @property
    def file_content(self):
        return self._get_content(self.file)

    @property
    def file2_content(self):
        return self._get_content(self.file2)

    REPLACE_EXT_RE = re.compile("\.[^\.]*$")

    # XXX used by others? otherwise it should be inlined
    def _get_expected_url(self, path):
        return self.REPLACE_EXT_RE.sub("-expected.txt", path)

    @property
    def expected_content(self):
        return self._get_content(self._get_expected_url(self.file))

# Listen for post_syncdb signal in order to flush the Test related objects cache
def _flush_test_m2m_obj_cache(*args, **kwargs):
    Test._m2m_obj_cache = {}
models.signals.post_syncdb.connect(_flush_test_m2m_obj_cache)


# XXX move elsewhere?
class TestBlacklist(models.Model):
    test = models.ForeignKey(Test)
    # Using a string identifier to avoid circular dependencies.
    ua = models.ForeignKey("useragents.Useragent")
    comment = models.CharField(max_length=300, blank=True)

    def __unicode__(self):
        return "Blacklist of test: %s with ua: %s" % (self.test, self.ua)

try:
    import browsertests.main.utils
    browsertests.main.utils.redirect___file__(globals())
except:
    pass
